#include <everest/dbc/odbc/odbc_common.h>
#include <vector>
#include <sstream>
#include <string.h>
#include <stdio.h>

using namespace NS_EVEREST::dbc::odbc;
using namespace NS_EVEREST::dbc;
using namespace NS_EVEREST;
using namespace std;


OdbcResultSet::OdbcResultSet(SQLHANDLE hStm, OdbcConnection* pCon, SQLSMALLINT cols)
: m_pCon(pCon), m_StmHandle(hStm), m_Columns(cols), m_PrevReturn(SQL_SUCCESS), m_RowBuffer(NULL)
{
	if ( cols > 0) m_ColDescVec.reserve(cols);
	SQLRETURN ret = this->bindColumns();
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::OdbcResultSet{1}/bindColumns()");
	} else if (ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::OdbcResultSet{1}/bindColumns()");
	}
}

OdbcResultSet::OdbcResultSet(SQLHANDLE hStm, OdbcConnection* pCon, SQLSMALLINT cols, SQLRETURN result)
: m_pCon(pCon), m_StmHandle(hStm), m_Columns(cols), m_PrevReturn(result), m_RowBuffer(NULL)
{
	if (m_PrevReturn == SQL_SUCCESS && cols > 0) {
		m_ColDescVec.reserve(cols);
		SQLRETURN ret = this->bindColumns();
		if ( ret == SQL_ERROR) {
			throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::OdbcResultSet{2}/bindColumns()");
		} else if (ret == SQL_INVALID_HANDLE) {
			throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::OdbcResultSet{2}/bindColumns()");
		}
	}
}

OdbcResultSet::~OdbcResultSet()
{
	try {
		this->close();
	} catch (...) {
		// EMPTY
	}
	if ( this->m_RowBuffer ) delete[] this->m_RowBuffer;
	this->m_RowBuffer = NULL;
}

void OdbcResultSet::release()
{
	delete this;
}

void OdbcResultSet::close()
{
	SQLRETURN ret = this->unbindColumns();
	this->m_StmHandle = SQL_NULL_HANDLE;
	this->m_Columns   = 0;
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::close()");
	} else if (ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::close()");
	}
}

SQLRETURN OdbcResultSet::bindColumns() throw()
{

	SQLRETURN ret = SQL_SUCCESS;
	SQLULEN   recordTotalLen = 0;
	// get column description
	//
	for(SQLSMALLINT i = 0; i < m_Columns; ++i) {
		OdbcColDesc desc;
		desc.colnum = i + 1;
		ret = SQLDescribeCol(
			m_StmHandle,
			desc.colnum,
			desc.colname,
			sizeof(desc.colname) / sizeof(SQLCHAR), // in characters
			&desc.namelen,
			&desc.datatype,
			&desc.colsize,
			&desc.decimaldigits,
			&desc.nullable);
		if ( !EVEREST_ODBC_SUCCESS(ret) ) break;
		printf("col %s type = %d, var = %d\n", desc.colname, desc.datatype, SQL_VARCHAR);

		if ( (size_t)desc.namelen >= sizeof(desc.colname) / sizeof(SQLCHAR)) {
			desc.pcolname = new SQLCHAR[desc.namelen + 1];
			ret = SQLDescribeCol(
				m_StmHandle,
				desc.colnum,
				desc.pcolname,
				desc.namelen + 1, // in characters
				&desc.namelen,
				&desc.datatype,
				&desc.colsize,
				&desc.decimaldigits,
				&desc.nullable);
			if ( !EVEREST_ODBC_SUCCESS(ret) ) break;
		}

		OdbcColDesc::transToCType(&desc);

		if ( desc.targetsize > 16 * 1024) {  // binding buffer no more then 16KB for each column
			desc.targetsize = 0;             //   these fields will be treated as CLOB.
		}

		if ( desc.targetsize > 0)  // only non-LOB field needs binding-buffer
			recordTotalLen += (desc.targetsize + sizeof(SQLLEN));  // SQLLEN for null-indicator
		printf("target %s type = %d, size = %ld\n", desc.colname, desc.targettype, desc.targetsize);
		this->m_ColDescVec.push_back(desc);
	} // for

	// allocate AND bind column buffer
	//
	if ( EVEREST_ODBC_SUCCESS(ret) ) {
		if ( m_RowBuffer ) delete[] m_RowBuffer;
		m_RowBuffer = new char[recordTotalLen];
		memset(m_RowBuffer, 0, recordTotalLen);
		SQLLEN position = 0;
		ColumnDescVector::iterator iter = m_ColDescVec.begin();
		while ( iter != m_ColDescVec.end()) {
			if ( iter->targetsize == 0) {   // LOB field should not bind
				++iter;
				continue;
			}

			iter->pIndOrLen = m_RowBuffer + position;
			if ( iter->colsize > 0 ) {
				iter->pValue = iter->pIndOrLen + sizeof(SQLLEN);
			} else {
				iter->pValue = NULL;
			}
			position += (iter->targetsize + sizeof(SQLLEN));

			ret = SQLBindCol(m_StmHandle, iter->colnum, iter->targettype, (SQLPOINTER)iter->pValue, iter->targetsize, (SQLLEN *)iter->pIndOrLen);
			if ( !EVEREST_ODBC_SUCCESS(ret)) break;
			++iter;
		} // end while
	}

	if ( !EVEREST_ODBC_SUCCESS(ret) ) {
		if ( m_RowBuffer ) delete[] m_RowBuffer;
		m_RowBuffer = NULL;
	}

	return ret;
}  // end of bindColumns

SQLRETURN OdbcResultSet::unbindColumns() throw()
{
	//if ( m_StmHandle == SQL_NULL_HANDLE) return SQL_INVALID_HANDLE;

	SQLRETURN result = SQL_SUCCESS;
	SQLRETURN ret = SQLFreeStmt(m_StmHandle, SQL_CLOSE);
	if (!EVEREST_ODBC_SUCCESS(ret)) result = ret;
	ret = SQLFreeStmt(m_StmHandle, SQL_UNBIND);
	if (!EVEREST_ODBC_SUCCESS(ret)) result = ret;
	ret = SQLFreeStmt(m_StmHandle, SQL_RESET_PARAMS);
	if (!EVEREST_ODBC_SUCCESS(ret)) result = ret;

	return result;
}

bool OdbcResultSet::first()
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_FIRST, 0);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::first()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::first()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool OdbcResultSet::last()
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_LAST, 0);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::last()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::last()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool OdbcResultSet::next()
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_NEXT, 0);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::next()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::next()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool OdbcResultSet::previous()
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_PRIOR, 0);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::previous()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::previous()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool OdbcResultSet::absolute(ssize_t row)
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_ABSOLUTE, row);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::absolute()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::absolute()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool OdbcResultSet::relative(ssize_t rows)
{
	SQLRETURN ret = SQLFetchScroll(m_StmHandle, SQL_FETCH_RELATIVE, rows);
	if ( ret == SQL_ERROR) {
		throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::relative()");
	} else if ( ret == SQL_INVALID_HANDLE) {
		throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::relative()");
	} else if ( ret == SQL_NO_DATA) {
		return false;
	}
	return true;
}

bool   OdbcResultSet::getByte(int col, char& val) const
{
	if ( col < 0 || col >= (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)this->m_ColDescVec.size(), "OdbcResultSet::getByte()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
	switch ( desc.targettype) {
	case SQL_C_SLONG:
		val = (char)(*((int *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "byte", "OdbcResultSet::getByte" );
	} // end switch

	return true;
}

bool  OdbcResultSet::getShort(int col, short& val) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getShort()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
	switch ( desc.targettype) {
	case SQL_C_SLONG:
		val = (short)(*((int *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "short", "OdbcResultSet::getShort()" );
	}
	return true;
}

bool    OdbcResultSet::getInt(int col, int& val) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getInt()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
	switch ( desc.targettype) {
	case SQL_C_SLONG:
		val = (*((int *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "int", "OdbcResultSet::getInt()" );
	}

	return true;
}

bool OdbcResultSet::getInt64(int col, int64_t& val) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getInt64()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
	switch ( desc.targettype) {
	case SQL_C_SLONG:
		val = (*((int *)desc.pValue));
		break;
	case SQL_C_SBIGINT:
		val = (*((int64_t *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "int64", "OdbcResultSet::getInt64()" );
	}
	return true;
}

bool  OdbcResultSet::getFloat(int col, float& val) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(),  "OdbcResultSet::getFloat()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
	switch ( desc.targettype) {
	case SQL_C_FLOAT:
		val = (*((float *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "float", "OdbcResultSet::getFloat()" );
	}
	return true;
}

bool OdbcResultSet::getDouble(int col, double& val) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getDouble()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL

	switch ( desc.targettype) {
	case SQL_C_FLOAT:
		val = (double)(*((float *)desc.pValue));
		break;
	case SQL_C_DOUBLE:
		val = (*((double *)desc.pValue));
		break;
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "double", "OdbcResultSet::getDouble()" );
	}

	return true;
}

bool   OdbcResultSet::getString(int col, String& str) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getString()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if (desc.targettype != SQL_C_CHAR) {
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "String", "OdbcResultSet::getString()" );
	}

	bool result = true;
	if ( desc.targetsize > 0) {
		if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL
		str.assign((const char *)desc.pValue);
	} else {
		SQLCHAR buf[256];
		SQLLEN  lenInd = 0;
		str.clear();

		while (true) {
			SQLRETURN ret = SQLGetData(m_StmHandle, col, SQL_C_CHAR, buf, 256, &lenInd);
			if ( ret == SQL_INVALID_HANDLE) {
				throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::getString()");
			} else if ( ret == SQL_ERROR ) {
				throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::getString()");
			} else if ( ret == SQL_NO_DATA) {
				break; // all data has returned
			} else if (EVEREST_ODBC_SUCCESS(ret)) { // success or success_with_info
				if ( lenInd == SQL_NO_TOTAL ) {
					break;  // all data has returned
				} else if ( lenInd == SQL_NULL_DATA) {
					result = false; // column value is null
					break;
				} else {
					if ( lenInd > (ssize_t)str.capacity()) str.reserve(lenInd);
					str.append((const char *)buf);
				}
			}
		} // end while
	}

	return result;
}

bool   OdbcResultSet::getBinary(int col, ByteVector& bytes) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getBinary()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( desc.targettype != SQL_C_BINARY)
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "Binary", "OdbcResultSet::getBinary()" );

	bool result = true;
	if ( desc.targetsize > 0) {
		bytes.resize(desc.targetsize);
		memcpy(&bytes[0], desc.pValue, desc.targetsize);
		return true;
	} else {
		char buf[256];
		SQLLEN  lenInd = 0, index = 0;
		bool vecInited = false;

		while (true) {
			SQLRETURN ret = SQLGetData(m_StmHandle, col, SQL_C_BINARY, buf, 256, &lenInd);
			if ( ret == SQL_INVALID_HANDLE) {
				throw OdbcException::InvalidHandle(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::getBinary()");
			} else if ( ret == SQL_ERROR ) {
				throw OdbcException(SQL_HANDLE_STMT, m_StmHandle, "OdbcResultSet::getBinary()");
			} else if ( ret == SQL_NO_DATA) {
				break; // all data has returned
			} else if (EVEREST_ODBC_SUCCESS(ret)) { // success or success_with_info
				if ( lenInd == SQL_NO_TOTAL ) {
					break;  // all data has returned
				} else if ( lenInd == SQL_NULL_DATA) {
					result = false; // column value is null
					break;
				} else {
					if ( !vecInited ) {
						bytes.resize(lenInd);
						vecInited = true;
					}
					lenInd = ( lenInd > 256 )?256:lenInd;
					memcpy(&bytes[index], buf, lenInd);
					index += lenInd;
				}
			}
		} // end while
	}

	return result;
}

bool   OdbcResultSet::getDateTime(int col, DateTime& dt) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getDateTime()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL

	switch ( desc.targettype) {
	case SQL_C_TYPE_DATE: {
		DATE_STRUCT *pDate = (DATE_STRUCT *)desc.pValue;
		memset(&dt, 0, sizeof(DateTime));
		dt.tm_year = pDate->year - 1900;
		dt.tm_mon  = pDate->month - 1;
		dt.tm_mday = pDate->day;
		break;}
	case SQL_C_TYPE_TIME: {
		TIME_STRUCT *pTime = (TIME_STRUCT *)desc.pValue;
		memset(&dt, 0, sizeof(DateTime));
		dt.tm_hour = pTime->hour;
		dt.tm_min  = pTime->minute;
		dt.tm_sec  = pTime->second;
		break;}
	case SQL_C_TYPE_TIMESTAMP: {
		TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)desc.pValue;
		dt.tm_year = pt->year - 1900;
		dt.tm_mon  = pt->month - 1;
		dt.tm_mday = pt->day;
		dt.tm_hour = pt->hour;
		dt.tm_min  = pt->minute;
		dt.tm_sec  = pt->second;
		dt.tm_isdst = 0;
		dt.tm_isdst = 0;
		dt.tm_wday  = 0;
		dt.tm_yday  = 0;
		break;}
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "struct_tm", "OdbcResultSet::getDateTime()" );
	}
	return true;
}

bool   OdbcResultSet::getTimestamp(int col, Timestamp& timestamp) const
{
	if ( col < 1 || col > (int)this->m_ColDescVec.size() )
		throw OdbcException::InvalidColumnIndex(col, 1, (int)m_ColDescVec.size(), "OdbcResultSet::getTimestamp()");
	const OdbcColDesc& desc = this->m_ColDescVec[col - 1];
	DateTime dt;

	if ( *desc.pIndOrLen == SQL_NULL_DATA ) return false;  // NULL

	switch ( desc.targettype) {
	case SQL_C_TYPE_DATE: {
		DATE_STRUCT *pDate = (DATE_STRUCT *)desc.pValue;
		timestamp.tv_sec = 0;
		timestamp.tv_usec = 0;
		dt.tm_year = pDate->year - 1900;
		dt.tm_mon  = pDate->month - 1;
		dt.tm_mday = pDate->day;
		break;}
	case SQL_C_TYPE_TIME: {
		TIME_STRUCT *pTime = (TIME_STRUCT *)desc.pValue;
		timestamp.tv_sec = 0;
		timestamp.tv_usec = 0;
		dt.tm_hour = pTime->hour;
		dt.tm_min  = pTime->minute;
		dt.tm_sec  = pTime->second;
		break;}
	case SQL_C_TYPE_TIMESTAMP: {
		TIMESTAMP_STRUCT *pt = (TIMESTAMP_STRUCT *)desc.pValue;
		timestamp.tv_sec = 0;
		timestamp.tv_usec = pt->fraction / 1000;
		dt.tm_year = pt->year - 1900;
		dt.tm_mon  = pt->month - 1;
		dt.tm_mday = pt->day;
		dt.tm_hour = pt->hour;
		dt.tm_min  = pt->minute;
		dt.tm_sec  = pt->second;
		dt.tm_isdst = 0;
		dt.tm_isdst = 0;
		dt.tm_wday  = 0;
		dt.tm_yday  = 0;
		break;}
	default:
		throw OdbcException::InvalidTypeCast(col, desc.targettype, "struct_timeval", "OdbcResultSet::getTimestamp()" );
	}
	time_t t = mktime(&dt);
	timestamp.tv_sec = t;
	return true;
}

